package energy;

import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;

import net.tinyos.comm.SerialPortEvent;
import net.tinyos.packet.*;
import java.util.Collections;
import java.util.List;
import java.util.LinkedList;

import java.io.IOException;

public class EnergyDriver implements Driver<EnergyData> {

	private static Logger log = Logger.getLogger("energy.EnergyDriver");
	private static final int PACKET_LENGTH = 10; 
	
	private SerialByteSource source;
	boolean running;
	Thread dataThread;
	
	private EnergyData lastData;
	private List<EnergyData> data;
	
	public EnergyDriver(String path) {
		source = new SerialByteSource(path, 115200);
		running = false;
		lastData = null;
		clearData();
		
		dataThread = new Thread(new Runnable() {
			public void run() {
				try {
					int i;
					byte[] bytes = new byte[PACKET_LENGTH];
					while(true) {
						for(i=0; i<PACKET_LENGTH; i++) {
							bytes[i] = source.readByte();
						}
						dataReceived(bytes);
					}
				} catch(IOException e) {
					log.error("Cannot read data from serial.");
					log.debug(e);
				}	
			}	
		}, "Data Thread");
  	}
	
	public void init() {
		try {
			source.open();
		} catch(IOException e) {
			log.error("Cannot open serial streams.");
			log.debug(e);
		}
		dataThread.start();
	}

	public synchronized void start() {
		if(source==null) {
			log.error("Driver needs to be initialized.");
			return;
		}
		log.debug("Starting..");
		running = true;
		lastData = null;
		clearData();
		sendCommand(Constants.ENERGY_ACTION_START);
	}
	
	public synchronized void stop() {
		log.debug("Stopping..");
		running = false;
		sendCommand(Constants.ENERGY_ACTION_STOP);
	}
	
	public synchronized void destroy() {
		if(running) {
			log.error("Cannot destroy a running Driver.");
			return;
		}
		dataThread.interrupt();
		source.close();
	}
	
	// Unsigned byte read
	private int ubyte(int val) {
		if (val < 0)
			return val + 256;
	    else
	      return val;
	  }
	
	synchronized private void dataReceived(byte[] bytes) {
		if(!running) {
			log.error("ERROR: Received data when we shouldn't be running!");
			return;
		}
	
		long seqno = (ubyte(bytes[3]) << 24) + (ubyte(bytes[2]) << 16) + (ubyte(bytes[1]) << 8) + ubyte(bytes[0]);
		long time  = (ubyte(bytes[7]) << 24) + (ubyte(bytes[6]) << 16) + (ubyte(bytes[5]) << 8) + ubyte(bytes[4]);
		byte status = bytes[8];
		
		/*String b = "";
		for(int i=0; i<PACKET_LENGTH; i++) b += " " + bytes[i];
		log.debug(b);*/
		//log.debug("status="+status+", seqno="+seqno+", time="+time);
		
		//log.debug(status);
		
		if(lastData==null) {
			EnergyData newData = new EnergyData(seqno, time, status);
			data.add(newData);
			lastData = newData;
		} else {
			//EnergyData newData = new EnergyData(time, lastData.energy + (seqno-lastSeqno)*ENERGY_PER_TURNAROUND, status);
			
			// Add lost data
			int diff = (int)seqno-lastData.seqno;
			for(int i=1; i<diff; i++) {
				data.add(new EnergyData(seqno, lastData.time + i*((time-lastData.time)/diff), status));
			}
			
			// Add current data
			EnergyData newData = new EnergyData(seqno, time, status);
			data.add(newData);
			lastData = newData;
		}
		
	}
	
	public synchronized List<EnergyData> getdata() {
		List<EnergyData> oldData = data;
		clearData();
		return Collections.unmodifiableList(oldData);
	}

	private void sendCommand(byte action) {
		byte[] bytes = new byte[] {action};
    	try {
    		source.writeBytes(bytes);
    	}
    	catch (IOException e) {
    		log.error("Cannot send configuration message to mote.");
    		log.debug(e);
    	}
	}
	
	private void clearData() {
		data = Collections.synchronizedList(new LinkedList<EnergyData>());
	}
	
	public static void main(String[] args) {
        BasicConfigurator.configure();
        Logger.getRootLogger().setLevel(Level.DEBUG);
        
        String path = "/dev/ttyUSB0";
        if(args.length>0) {
        	path = args[0];
        }
        EnergyDriver driver = new EnergyDriver(path);
        driver.init();
        UserInterface gui = new UserInterface(driver);
        gui.start();
	}

}
